#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         ClassInfo.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  A ClassInfo instance provides minimal runtime type
#               and meta information about instances of the
#               GenericObject class.
#
#               ClassInfo instances are registered with the Knowledge
#               Engine Core when the framework starts and may be used
#               to gain information about GenericObject instances
#               without having to actually instanciate a GenericObject.
#
#               Additionally ClassInfo instances are designed to
#               provide enough runtime type information that the 
#               framework may easily instanciate and correctly 
#               initialise GenericObject instances without directly 
#               performing hard-coded calls to GenericObject constructors.
#
# Note:         Access to an instance of this class is provided by
#               calling the getClassInfo() method of a GenericObject 
#               instance.
#
# See Also:     GenericObject, PropertyReflection, PropertyDefinition, 
#               EventReflection, EventDefinition 
#####################################################################

#####################################################################
## KnowledgeEngine Library Imports
from Common import *
from PropertyReflection import PropertyReflection
from EventReflection import EventReflection
from PermissionDefinition import PermissionDefinition

#####################################################################
## Zope Library Imports
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.Role import RoleManager

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## ClassInfo Class Definition
class ClassInfo(RoleManager, PropertyReflection, EventReflection):
    """
        ABSTRACT: Base class providing information about a class
    """

    #---------------------------------------------------------------#

    # FINAL: A reference to the actual python Class instance. 
    #        (Python represents actual classes as objects.  Hence
    #         class implementations may be modified at runtime
    #         simply by modifying the class instances)
    #
    # Automatically set when a class is registered with registerClassInfo
    classReference = None

    #---------------------------------------------------------------#

    #The following instance variable are created at runtime as needed
    #propertyDefinitions = {}
    #eventDefinitions = {}
    #permissionDefinitions = {}
        
    #---------------------------------------------------------------#

    #declare security so ClassInfo may be accessed in DTML/ZPT etc
    security = ClassSecurityInfo()
    security.declareObjectPublic()
    security.setDefaultAccess("allow")
        
    #---------------------------------------------------------------#

    def getClassName(self):
        """
            ABSTRACT: Return the name of the class
        """
    
        return "ClassInfo"

    #---------------------------------------------------------------#

    def getClassFamilyName(self):
        """
            Return the name of the family to which the the
            class (and subclasses) and it's subclasses belong/
            
            This is used to enable searching for families of 
            generic objects, eg: 'EntitySource' instances.
            
            By default the family name is the same as the class name
        """

        return self.getClassName()

    #---------------------------------------------------------------#

    def getClass(self):
        """
            FINAL: Return a reference to the actual class
        """
        
        return self.classReference
    
    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            ABSTRACT: Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Class Information"
        
    #---------------------------------------------------------------#

    def getDescription(self):
        """
            ABSTRACT: Return a friendly description on the purpose
            of this class.  This description is often used in the 
            Property Editor for instances of this class
        """
    
        return ""
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            ABSTRACT: Return the identity type.  
            
            This is a short string used for prefixing object identities
        """
        
        return "unknown"
        
    #---------------------------------------------------------------#

    def getIcon(self, relative_to_portal=0):
        """
            PLONE Support:
            
            Returns a URL to the icon for the object
        """
                
        return self.getClassName() + '_icon.gif'
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            ABSTRACT: Return the appropriate zope icon image file for the class.
            All concrete classes must provide an icon!
            
            TODO: I'M SURE WE CAN REMOVE THIS NOW
        """
        
        return None
        
    #---------------------------------------------------------------#
    
    def getClassIconHTML(self):
        """
            Returns appropriate HTML to render the icon for the class

            TODO: I'M SURE WE CAN REMOVE THIS NOW
        """
        
        return '<img src="/misc_/KnowledgeEngine/%s.gif" alt="%s" valign="absmiddle" border="0" />' % (self.getClassName(), self.getDisplayName)
        
    #---------------------------------------------------------------#
        
    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified 
            by the className
        """

        return className == self.getClassName() or (self.getClass() <> None and className == self.getClass().__name__)

    #---------------------------------------------------------------#

    def isContainer(self):
        """
            Returns if the instance is providing folderish/container
            services.
            
            Override an return true if the instance is to be
            a container for other instances.
            
            NOTE: all instances support "events" so there is no
            need to return true if you require "event" support.
        """

        #by default, instances are not containers 
        #(they support being a container, but we don't want to display that)
        return false          

    #---------------------------------------------------------------#

    def isPortalObject(self):
        """
            Returns if the object can be placed directly in 
            a Plone Portal (rather than like most objects
            that must exist within a GenericObject container
            object.
            
            eg: Models, EntitySources are Portal Objects.
        """
        
        return false

    #---------------------------------------------------------------#

    def newInstance(self, id, identity, REQUEST=None):
        """
            Creates and returns an instance of the class for which
            this class is defining class information.
            
            This method may be overriden by subclasses to provide
            specialised object construction using a REQUEST.
            
            This method is specifically called by 
            GenericObject.manage_ObjectConstruction to construct
            instances of requested classes.
            
            eg: Calling CheckboxInfo.newInstance(...) will create
            a new instance of the Checkbox class.
        """
        
        return self.getClass()(id, identity)
        
    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in 
            the specified container (containerInstance)
            
            NOTE: This is from this class' instance perspective.
        """
        
        #the default is not permitted
        return false

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            ABSTRACT: Returns if the specified child is permitted as a 
            child of the class' instances.
            
            NOTE: This is from this class' instance perspective.
            
            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """
        
        #the default is not permitted
        return false
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
        
        #Create the propertyDefinitions attribute for the class
        #only if it doesn't already have it.  We do this because
        #this method may be called many times by classes that
        #use multiple inheritance/mix-ins.  Without this guard
        #we would 'clobber' previously defined property definitions.
        if not hasattr(self, 'propertyDefinitions'):
            self.propertyDefinitions = {}
    
    #---------------------------------------------------------------#

    def onAfterStartup(self):
        """
            Called after the Knowledge Engine has been started
            and all of the appropriate classes have been initialized.

            This method is useful for performing class specific 
            backwards/compatibility operations once the Knowledge
            Engine has been started.
        """
        
        pass
        
    #---------------------------------------------------------------#

    def getNrProperties(self):
        """
            Return the number of properties defined
            by the class implementing this interface
        """
        
        return len(self.propertyDefinitions)
        
    #---------------------------------------------------------------#
        
    def isPropertyDefined(self, propertyIdentity):
        """
            Return if the specified property is defined 
            by the class implementing this interface. 
        """

        return self.propertyDefinitions.has_key(propertyIdentity)
        
    #---------------------------------------------------------------#
        
    def getPropertyDefinition(self, propertyIdentity):
        """
            Return the PropertyDefinition for the specified
            property identity. Returns None if the proeprty definition
            is unknown

            RETURN TYPE: PropertyDefinition
        """

        if self.isPropertyDefined(propertyIdentity):
            return self.propertyDefinitions[propertyIdentity]
        else:
            return None
        
    #---------------------------------------------------------------#
        
    def getPropertyDefinitions(self):
        """
            Return the PropertyDefinitions for the
            class implementing this interface

            RETURN TYPE: [PropertyDefinition]
        """

        propertyDefinitions = self.propertyDefinitions.values()        
        propertyDefinitions.sort(lambda x, y : -(x.getDisplayOrder() < y.getDisplayOrder()))
        return propertyDefinitions
        
    #---------------------------------------------------------------#
        
    def addPropertyDefinition(self, propertyDefinition):
        """
            Adds/overrides the specified property definition to/in
            the class implementing this interface
        """
        
        #does the property have a display order (other than 0)
        #(if it doesn't we add the property to the end of the already registered properties)
        if propertyDefinition.getDisplayOrder() == 0:
            
            #get the property definitions
            propertyDefinitions = self.getPropertyDefinitions()
            if len(propertyDefinitions) == 0:
                nextOrder = 1
            else:
                #determine the next available display order
                nextOrder = max([pd.getDisplayOrder() for pd in propertyDefinitions]) + 1

            #update the display order
            propertyDefinition.setDisplayOrder(nextOrder)
            
        self.propertyDefinitions[propertyDefinition.getIdentity()] = propertyDefinition
        
    #---------------------------------------------------------------#
        
    def removePropertyDefinition(self, propertyIdentity):
        """
            Removes the specified property definition
            from the class implementing this interface
        """

        del self.propertyDefinitions[propertyIdentity]
        
    #---------------------------------------------------------------#

    def hasEditableConstructorProperties(self):
        """
            Returns if the class implementing this
            interface has editable constructor properties
        """
        
        #attempt to find a property that can be edited on a 
        #constructor property editor
        for propertyDefinition in self.getPropertyDefinitions():
            if propertyDefinition.requiresConstructor():
                #we've found one, so exit now - no need to continue searching
                return true
        
        return false
        
    #---------------------------------------------------------------#

    def hasEditableProperties(self):
        """
            Returns if the class implementing this
            interface has editable properties
        """        
        
        #attempt to find a property that can be edited on a 
        # property editor
        for propertyDefinition in self.getPropertyDefinitions():
            if propertyDefinition.requiresPropertyEditor():
                #we've found one, so exit now - no need to continue searching
                return true
        
        #none found!
        return false
    
    #---------------------------------------------------------------#

    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific events using the EventReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
        
        #Create the eventDefinitions attribute for the class
        #only if it doesn't already have it.  We do this because
        #this method may be called many times by classes that
        #use multiple inheritance/mix-ins.  Without this guard
        #we would 'clobber' previously defined event definitions.
        if not hasattr(self, 'eventDefinitions'):
            self.eventDefinitions = {}
        
    #---------------------------------------------------------------#

    def hasEventDefinitions(self):
        """
            Return if the defining class exposes events
            that may be handled by actions
        """
        
        return hasattr(self, 'eventDefinitions') and len(self.eventDefinitions) > 0
        
    #---------------------------------------------------------------#
        
    def isEventDefined(self, eventIdentity):
        """
            Return if the specified event is defined 
            by the class implementing this interface. 

            RETURN TYPE: Boolean
        """

        return self.eventDefinitions.has_key(eventIdentity)
        
    #---------------------------------------------------------------#
        
    def getEventDefinition(self, eventIdentity):
        """
            Return the EventDefinition for the specified
            event identity. Returns None if the event definition
            is unknown

            RETURN TYPE: EventDefinition
        """

        if self.isEventDefined(eventIdentity):
            return self.eventDefinitions[eventIdentity]
        else:
            return None
        
    #---------------------------------------------------------------#
        
    def getEventDefinitions(self):
        """
            Return the EventDefinitions for the
            class implementing this interface

            RETURN TYPE: [EventDefinition]
        """

        eventDefinitions = self.eventDefinitions.values()        
        eventDefinitions.sort(lambda x, y : -(x.getIdentity() < y.getIdentity()))
        return eventDefinitions
        
    #---------------------------------------------------------------#
        
    def addEventDefinition(self, eventDefinition):
        """
            Adds/overrides the specified event definition to/in
            the class implementing this interface
        """

        self.eventDefinitions[eventDefinition.getIdentity()] = eventDefinition
    
    #---------------------------------------------------------------#

    def onConfigurePermissions(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific permissions.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
        
        #Create the permissionDefinitions attribute for the class
        #only if it doesn't already have it.  We do this because
        #this method may be called many times by classes that
        #use multiple inheritance/mix-ins.  Without this guard
        #we would 'clobber' previously defined permission definitions.
        if not hasattr(self, 'permissionDefinitions'):
            self.permissionDefinitions = {}
        
    #---------------------------------------------------------------#

    def hasPermissionDefinitions(self):
        """
            Return if the defining class defines permissions
        """
        
        return hasattr(self, 'permissionDefinitions') and len(self.permissionDefinitions) > 0
        
    #---------------------------------------------------------------#
        
    def isPermissionDefined(self, permissionIdentity):
        """
            Return if the specified permission is defined 
            by the class implementing this interface. 

            RETURN TYPE: Boolean
        """

        return self.permissionDefinitions.has_key(permissionIdentity)
        
    #---------------------------------------------------------------#
        
    def getPermissionDefinition(self, permissionIdentity):
        """
            Return the PermissionDefinition for the specified
            permisison identity. Returns None if the permission definition
            is unknown

            RETURN TYPE: EventDefinition
        """

        if self.isPermissionDefined(permissionIdentity):
            return self.permissionDefinitions[permissionIdentity]
        else:
            return None
        
    #---------------------------------------------------------------#
        
    def getPermissionDefinitions(self):
        """
            Return the PermissionDefinitions for the
            class implementing this interface

            RETURN TYPE: [PermissionDefinition]
        """

        permissionDefinitions = self.permissionDefinitions.values()        
        permissionDefinitions.sort(lambda x, y : -(x.getDisplayName() < y.getDisplayName()))
        return permissionDefinitions
        
    #---------------------------------------------------------------#
        
    def addPermissionDefinition(self, permissionDefinition):
        """
            Adds/overrides the specified permission definition to/in
            the class implementing this interface
        """

        self.permissionDefinitions[permissionDefinition.getIdentity()] = permissionDefinition
        
    #---------------------------------------------------------------#
        
#####################################################################
## Class Initialisation
InitializeClass(ClassInfo)
